
package genetic2;

import java.io.Serializable;
import java.util.*;
import java.util.Map.*;

public class Individual implements Serializable , Comparable<Individual>  {
   private int fitness;
   private int sizeInKb;
   private Map<String, List<ColumnStatistics>> cubes;
   private Random r = new Random();
   
   public Individual(){
       cubes = new HashMap<String, List<ColumnStatistics>>();
   }

   public Individual(final Map<String, List<ColumnStatistics>> allCubes, final int maxSizeKb){
       this.cubes = pickInitCubes(allCubes, maxSizeKb);
       calcFitness(maxSizeKb);
   }
    
   private Map<String, List<ColumnStatistics>> pickInitCubes(final Map<String, List<ColumnStatistics>> allCubes,
           int maxSizeKb){
        final Map<String, List<ColumnStatistics>> cubes = new HashMap<String, List<ColumnStatistics>>();
        maxSizeKb = (int)(GeneticParameters.INIT_MAX_FOLD * maxSizeKb * 1000);
        int curSizeB = 0; 
        
        for(Entry<String, List<ColumnStatistics>> entry : allCubes.entrySet()){
            if(r.nextBoolean()){
                final List<ColumnStatistics> columnStatistics2 = new ArrayList<ColumnStatistics>();
                for(ColumnStatistics cs : entry.getValue()){
                    if(cs.probabilityOfUsage > 0.0f)
                        columnStatistics2.add(cs);
                }
                
                cubes.put(entry.getKey(), columnStatistics2);
            }
        }
        
        return cubes;
    }
   
    public double getFitness() {
        return fitness;
    }

    public void setFitness(int fitness) {
        this.fitness = fitness;
    }

   public void calcFitness(final int maxSizeInKb) {
        calcSize();
        
        fitness = -Math.abs(maxSizeInKb - sizeInKb) * 100 + getAllProb();
        //System.out.println("size =" + (maxSizeInKb - sizeInKb) +  " totProb"+getAllProb() +" fit"+fitness);
   }
   
   public int getAllProb(){
       double prob = 0.0f; 
       for(Entry<String, List<ColumnStatistics>> entry : cubes.entrySet()){
            if(entry.getValue().size() <= 1)
                continue;
            
            for(ColumnStatistics st : entry.getValue()){
                if(st.fullCount == 0 )
                    continue;
                prob += st.probabilityOfUsage * ((double)st.count / st.fullCount); 
            }
        }
       
       return (int)(prob * 100);
   }
   
   public void crossover(final Individual mom , final Individual dad, final int maxSizeKb){ 
       final Map<String, List<ColumnStatistics>> momCubes = mom.getCubes();
       final Map<String, List<ColumnStatistics>> dadCubes = dad.getCubes();
       
       final Set<String> childTables = new HashSet<String>();
       
       for(String momTable : momCubes.keySet()){
           if(dadCubes.keySet().contains(momTable)){
               childTables.add(momTable);
           }
           else{
               if(r.nextBoolean()){
                    childTables.add(momTable);
               }
           }
       }
       
      for(String dadTable : dadCubes.keySet()){
           if(!childTables.contains(dadTable) && r.nextBoolean()){
                childTables.add(dadTable);
           }
       }
      
      for(String childTable : childTables){
          final List<ColumnStatistics> momList = momCubes.get(childTable);
          final List<ColumnStatistics> dadList = dadCubes.get(childTable);
          final List<ColumnStatistics> childList = new ArrayList<ColumnStatistics>();
          final Set<String> columnNames = new HashSet<String>();
          final int totSize = (momList == null ? 0 : momList.size()) + (dadList == null ? 0 : dadList.size());
          if(momList != null){
              for(ColumnStatistics st :momList){
                  if(r.nextInt(totSize) < momList.size()){
                      columnNames.add(st.name);
                  }
              }
          }
          
          if(dadList != null){
              for(ColumnStatistics st :dadList){
                  if(r.nextInt(totSize) < dadList.size()){
                      columnNames.add(st.name);
                  }
              }
          }
          
          for(String columnName : columnNames){
              ColumnStatistics parentST = getColumn(momList, columnName);
              if(parentST == null){
                   parentST = getColumn(dadList, columnName);
              }
              else {
                  if(r.nextBoolean() && getColumn(dadList, columnName) != null){
                      parentST = getColumn(dadList, columnName);
                  }
              }
              
              childList.add((ColumnStatistics)parentST.clone());
          }
          
          cubes.put(childTable, childList);
      }
      
      calcFitness(maxSizeKb);
   }
   
   private ColumnStatistics getColumn(final List<ColumnStatistics> list, final String col){
       if(list == null || list.isEmpty())
           return null;
       
       for(ColumnStatistics c : list){
           if(c.name.equalsIgnoreCase(col))
               return c;
       }
       
       return null;
   }

   public void mutate(final int sizeInKb){
       if( r.nextInt( GeneticParameters.PERCENTOF ) <= GeneticParameters.MUTATION_HALF)
           mutateHalf();
       else 
           cutColumn();
       calcFitness(sizeInKb);
   }
   
   
   public void cutColumn(){
        for(String tableName : cubes.keySet()){
            final List<ColumnStatistics> list = cubes.get(tableName);
            if(list == null || list.isEmpty())
                continue;
             if(list.size() == 1 && list.get(0).name.contains("id"))
                 list.remove(0);      
        }
   }
   
   public void mutateHalf(){
        int pos = r.nextInt(cubes.entrySet().size());
        for(String tableName : cubes.keySet()){
            if(pos == 0){
               final List<ColumnStatistics> list = cubes.get(tableName);
               int maxSize =0;
               for(ColumnStatistics stats : list){
                   if(stats.sizeInB > maxSize){
                       maxSize = stats.sizeInB;
                   }
               }
               
               for(ColumnStatistics stats : list){
                   if(stats.sizeInB == maxSize){
                      stats.sizeInB /= 2;
                      stats.count /= 2;
                      break;
                   }
               }
               
               break;
            }
            
            pos--;
        }
   }

    public Map<String, List<ColumnStatistics>> getCubes() {
        return cubes;
    }

    public int getSizeInKb() {
        return sizeInKb;
    }
    
    private void calcSize() {
        double totalSizeInB = 0;
        for(Entry<String, List<ColumnStatistics>> entry : cubes.entrySet()){
            if(entry.getValue() == null)
                continue;
            for(ColumnStatistics st : entry.getValue()){
                totalSizeInB += st.sizeInB;
            }
        }
        
        sizeInKb = (int)(totalSizeInB / 1024.0);
    }
   
    @Override
    public int compareTo(Individual o) {
        return (o.fitness - fitness);
    }

    public String toString() {
        final StringBuilder sb  = new StringBuilder("fitness=" + fitness + ", sizeInKb=" + sizeInKb+"\n");
        for(Entry<String, List<ColumnStatistics>> entry : cubes.entrySet()){
            sb.append(entry.getKey());
            sb.append("\t\t");
            
            for(ColumnStatistics cs : entry.getValue()){
                sb.append(cs.name);
                sb.append("["+ ((double)cs.count/ cs.count)+"]");
                sb.append("["+ cs.probabilityOfUsage+"]");
                sb.append(" ");
            }
             sb.append("\n");
        }
                
       return sb.toString();
    }


   
    @Override
    public Object clone(){
        final Individual i = new Individual();
        i.cubes = cloneCubes();
        i.fitness = fitness;
        i.setFitness(fitness);
        i.sizeInKb = sizeInKb;
        return i;
    } 
    
    private Map<String, List<ColumnStatistics>> cloneCubes(){
        final Map<String, List<ColumnStatistics>> cloneMap = new HashMap<String, List<ColumnStatistics>>();
        
        for(Entry<String, List<ColumnStatistics>> entry : cubes.entrySet()){
            final List<ColumnStatistics> cols = new ArrayList<ColumnStatistics>(entry.getValue().size());
            
            for(ColumnStatistics col : entry.getValue()){
                cols.add((ColumnStatistics)col.clone());
            }
            
            cloneMap.put(entry.getKey(), cols);
        }
        return cloneMap;
    }
    
//    public static void main(String... a){
//        List<Individual> ar = new ArrayList<Individual>();
//        Individual a1 = new Individual();
//        a1.setFitness(0.1);
//        
//        Individual a2 = new Individual();
//        a2.setFitness(0.2);
//        
//        ar.add(a1); ar.add(a2);
//        Collections.sort(ar);
//        
//        for(Individual aa : ar){
//            System.out.println(""+aa.getFitness());
//        }
//    }
}
